package com.ficus.road.maintain.process;

import com.ficus.road.maintain.core.model.Confirmation;
import com.ficus.road.maintain.core.model.FrameDTO;
import com.ficus.road.maintain.core.model.Hit;
import com.ficus.road.maintain.core.model.RoadDiseaseResult;
import com.ficus.road.maintain.service.HitService;
import com.ficus.road.maintain.util.queue.BlockingQueueMonitorDecorator;
import com.ficus.road.maintain.websocket.WebSocketReplayManager;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@Slf4j
public class ReplayDrawerProcessor extends Thread {

    //12.5, research设定了每秒12.5帧图片
//    volatile Double qps = 13d;
    public AtomicBoolean run;
    WebSocketReplayManager webSocketReplayManager;
    BlockingQueueMonitorDecorator<FrameDTO> blockingDeque = new BlockingQueueMonitorDecorator<>("replayqueue", new ArrayBlockingQueue<>(200));
    volatile RateLimiter rateLimiter;
    HitService hitService;
    ExpiringMap<Long, List<Hit>> map;
    double rateLimiterThreshold;

    long successTime;

    public boolean isWsAvailable() {
        return wsAvailable;
    }

    volatile boolean wsAvailable;

    // 超时时间5分钟
    long timeout = 5 * 60 * 1000;

    public ReplayDrawerProcessor(String name, WebSocketReplayManager webSocketReplayManager, HitService hitService, double rateLimiterThreshold) {
        super(name);
        this.webSocketReplayManager = webSocketReplayManager;
        map = ExpiringMap.builder()
                .maxSize(500)
                .expiration(180, TimeUnit.SECONDS)
                .expirationPolicy(ExpirationPolicy.ACCESSED)
                .variableExpiration()
                .build();
        rateLimiter = RateLimiter.create(rateLimiterThreshold);
        run = new AtomicBoolean(true);
        this.hitService = hitService;
        this.rateLimiterThreshold = rateLimiterThreshold;
        this.successTime = System.currentTimeMillis();
        this.wsAvailable = true;
    }

    @Override
    public void run() {
        while (run.get()) {
            try {
//                if (getToken()) {
//                    //通过两个子类获取FrameDTO
//                    //DrawerProcessor用的是延迟队列，ReplayDrawerProcessor用的是自己实现的队列
//                    FrameDTO value = getItem();
//                    //处理FrameDTO
//                    onProcess(value);
//                } else {
//                    log.info("get rateLimiter's permits failed");
//                }
                rateLimiter.acquire();
                FrameDTO value = getItem();
//                log.info("take frameData from ws queue,uuid={}", value.getUuid());
                onProcess(value);
            } catch (Exception e) {
                log.error("Base message processor error", e);
            }
        }
    }

    public void stopThread() {
        run.set(false);
    }

    public void put(FrameDTO value) {
        if (null == value) {
            return;
        }
        try {
            blockingDeque.put(value);
//            log.info("put frameData to send ws, uuid = {}", value.getUuid());
        } catch (InterruptedException e) {
            log.error("put into blockingQueue met exception:", e);
        }
    }

    public void putReplayHits(Map<Long, List<Hit>> hitsMap, long time) {
        map.clear();
        map.setExpiration(time, TimeUnit.MILLISECONDS);
        map.putAll(hitsMap);
    }

    public FrameDTO getItem() throws InterruptedException {
        return blockingDeque.take();
    }

    public boolean getToken() {
//        return rateLimiter.tryAcquire();
        return rateLimiter.tryAcquire(1, (long) (1 / rateLimiterThreshold), TimeUnit.SECONDS);

    }

    public void closeWs(String uuid) {
        while (!blockingDeque.isEmpty()) {
            try {
                Thread.sleep(50L);
            } catch (InterruptedException e) {
                log.error("Thread sleep met Exception:", e);
            }
        }
        webSocketReplayManager.closeSession(uuid);
    }

    public void onProcess(FrameDTO value) {
        //找到这条历史告警，看是否审核通过，这里需要把状态加过来
        String diseaseUid;
        //大部分数据帧里并没有病害，但是每一帧都有diseaseUid
        //找出有病害的帧
        boolean checked = false;
        if (CollectionUtils.isNotEmpty(value.getHits())) {
            for (Hit hit : value.getHits()) {
                diseaseUid = hit.getAttrs().getDiseaseUid();
                if (StringUtils.isNotBlank(diseaseUid)) {
                    Criteria criteria = Criteria.where("list.attrs.disease_uid").is(diseaseUid).and("attrs.confirmation.status").is(Confirmation.ConfirmationStatus.CORRECT.toString());
                    List<Hit> hits = hitService.queryHits(new Query(criteria));
                    if (CollectionUtils.isNotEmpty(hits)) {
                        checked = true;
                        hit.getAttrs().getConfirmation().setStatus(Confirmation.ConfirmationStatus.CORRECT);
                        break;
                    }
                }
            }
        }

        Long timeStamp = value.getTimeStamp();

        // 如果告警审核没通过，roadDiseaseResult就设为空
        if (!checked) {
            value.setRoadDiseaseResult(new ArrayList<>());
            value.setHits(new ArrayList<>());
        }
        value.setTimeStamp(timeStamp / 1000);
        value.setSendTimeStamp(value.getSendTimeStamp() / 1000);
        //视频回放的时候传入uuid

        List<Hit> mockHits = map.get(timeStamp);
        if (CollectionUtils.isNotEmpty(mockHits)) {
            List<RoadDiseaseResult> roadDiseaseResultList = convertHitToRoadDisease(mockHits);
            value.getRoadDiseaseResult().addAll(roadDiseaseResultList);
            value.getHits().addAll(mockHits);
        }
        boolean success = webSocketReplayManager.sentMessageById(value, value.getUuid());
        long now = System.currentTimeMillis();
        if (success) {
          successTime = now;
        }
        if (now - successTime >= timeout) {
            wsAvailable = false;
        }
    }

    List<RoadDiseaseResult> convertHitToRoadDisease(List<Hit> hits) {
        List<RoadDiseaseResult> roadDiseaseResultList = hits.stream().map(item ->
                RoadDiseaseResult.builder()
                        .type(item.getType()).roadType(item.getRoadType())
                        .angle(item.getAngle()).bbox(item.getSubjects().get(0).getBbox())
                        .relBbox(item.getSubjects().get(0).getRelBbox()).width(item.getWidth())
                        .status(item.getAttrs().getStatus()).severityType(item.getSeverityType())
                        .scene(item.getScene()).length(item.getLength())
                        .heightDiff(item.getHeightDiff()).headCount(item.getHeadCount())
                        .fallDownNumber(item.getFallDownNumber()).comfortRate(item.getComfortRate())
                        .build()
        ).collect(Collectors.toList());
        return roadDiseaseResultList;
    }

}
